package cn.tangsihai.hrm.service.impl;

import cn.tangsihai.hrm.constant.Constants;
import cn.tangsihai.hrm.domain.CourseType;
import cn.tangsihai.hrm.error.ErrorCode;
import cn.tangsihai.hrm.mapper.CourseTypeMapper;
import cn.tangsihai.hrm.mark.MarkCode;
import cn.tangsihai.hrm.service.ICourseTypeService;
import cn.tangsihai.hrm.util.AssertUtil;
import cn.tangsihai.hrm.vo.CrumbsVo;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.apache.commons.lang.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.sql.Wrapper;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author sihai.tang
 * @since 2021-11-26
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    // 获取Redis工具类对象
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CourseTypeMapper courseTypeMapper;
    // 当数据库发生变化时同步更新Redis中的数据，实现：先处理数据库，再删除Redis
    // 方式一：手动删除。 方式二：使用SpringCache注解@CacheEvict自动删除
    @Override
    // 方式二：使用注解
    @CacheEvict(cacheNames = MarkCode.ALL_COURSETYPES_BY_REDIS_KEY, key = MarkCode.ALL_COURSETYPES_BY_REDIS)
    public boolean insert(CourseType entity) {
        // 先添加到数据库
        boolean insert = super.insert(entity);
        // 再删除Redis中数据
        // 方式一：手动
        // redisTemplate.delete(MarkCode.ALL_COURSETYPES_BY_REDIS);
        return insert;
    }

    @Override
    // 方式二：使用注解
    @CacheEvict(cacheNames = MarkCode.ALL_COURSETYPES_BY_REDIS_KEY, key = MarkCode.ALL_COURSETYPES_BY_REDIS)
    public boolean deleteById(Serializable id) {
        // 先删除数据库中数据
        boolean deleteById = super.deleteById(id);
        // 再删除Redis中数据
        // 方式一：手动
        // redisTemplate.delete(MarkCode.ALL_COURSETYPES_BY_REDIS);
        return deleteById;
    }

    @Override
    // 方式二：使用注解
    @CacheEvict(cacheNames = MarkCode.ALL_COURSETYPES_BY_REDIS_KEY, key = MarkCode.ALL_COURSETYPES_BY_REDIS)
    public boolean updateById(CourseType entity) {
        // 先修改数据库中数据
        boolean updateById = super.updateById(entity);
        // 再删除Redis中数据
        // 方式一：手动
        // redisTemplate.delete(MarkCode.ALL_COURSETYPES_BY_REDIS);
        return updateById;
    }

    /**
     * 查询缓存：
     * 方式一：
     * 1.从Redis中查询数据
     * 2.存在，直接返回数据
     * 3.不存在，从数据库中查询数据
     * 4.将从数据库中查询出来的数据组合成需要的格式
     * 5.将数据存入Redis中
     * 6.返回数据
     * 方式二：
     * 使用SpringCache缓存
     * @return
     */
//    @Override
    public List<CourseType> getTreeData1() {
        // 1.从Redis中查询数据
        List<CourseType> coursetypes = (List<CourseType>)redisTemplate.opsForValue().get(MarkCode.ALL_COURSETYPES_BY_REDIS);
        if (coursetypes != null){ // 2.存在，直接返回数据
            return coursetypes;
        }
        // 3.不存在，从数据库中查询数据
        // 4.将从数据库中查询出来的数据组合成需要的格式
        coursetypes = getTreeDataFromMySQL();
        // 断言，当数据为空时打印异常
        AssertUtil.isNotNull(coursetypes, ErrorCode.SYSTEM_ABNORMAL.getErrorMessage());
        // 5.将数据存入Redis中
        redisTemplate.opsForValue().set(MarkCode.ALL_COURSETYPES_BY_REDIS, coursetypes, 12, TimeUnit.HOURS);
        // 6.返回数据
        return coursetypes;
    }

    /**
     * SpringCache缓存
     * 流程与手动的流程一致
     * @return
     */
    @Override
    // Cacheable：先从Redis中查询，有就直接返回，没有就执行方法
    @Cacheable(cacheNames = MarkCode.ALL_COURSETYPES_BY_REDIS_KEY, key = MarkCode.ALL_COURSETYPES_BY_REDIS)
    public List<CourseType> getTreeData() {
        // 3.不存在，从数据库中查询数据
        // 4.将从数据库中查询出来的数据组合成需要的格式
        List<CourseType> treeDataFromMySQL = getTreeDataFromMySQL();
        // 6.返回数据
        return treeDataFromMySQL;
    }

    /**
     * 获取类型树
     * 方式一：
     *      1.查出第一层
     *      2.将第一层的id作为条件查询第二层数据添加到第一层的children中
     *      3.将第二层的id作为条件查询第三层数据添加到第二层的children中
     *      4. 。。。
     *  实现：1.固定的层级，使用for循环。2.不固定层级，使用递归/MtBatis嵌套查询
     *  缺点：如果数据量大，会发送大量sql语句，效率极低
     * @return
     */
//    public List<CourseType> getTreeData() {
//        return courseTypeMapper.getTreeData(0L);
//    }

    /**
     * 方式二：
     *      1.查询出所有分类
     *      2.找出所有第一层
     *      3.其他非底层只需要找到自己的上一层，并加入其中children中
     */
    public List<CourseType> getTreeDataFromMySQL() {
        // 查询出所有分类
        List<CourseType> allCourseTypes = super.selectList(null);
        // 用来存放第一层分类
        ArrayList<CourseType> firstCourseTypes = new ArrayList<>();
        //
        allCourseTypes.forEach(courseType -> {
            if (courseType.getPid() == null || courseType.getPid().longValue() == 0L){ // 找出第一层分类
                firstCourseTypes.add(courseType);
            } else { // 非第一层
                for (CourseType allCourseType : allCourseTypes) { // 找出其他分类
                    // 非第一层分类来找自己的上一级
                    if (courseType.getPid().longValue() == allCourseType.getId().longValue()){
                        // 加到children中
                        allCourseType.getChildren().add(courseType);
                    }
                }
            }
        });
        return firstCourseTypes;
    }

    // 优化,因为上面那种是嵌套for循环，当数据量大时，十分影响性能
    public List<CourseType> getTreeDataFromMySQL2() {
        // 查询出所有分类
        List<CourseType> allCourseTypes = super.selectList(null);

        // 将数据存入map中，根据pid取就一定时自己的父分类
        HashMap<Object, Object> allCourseTypeMap = new HashMap<>();
        allCourseTypes.forEach(courseType -> {
            allCourseTypeMap.put(courseType.getId(), courseType);
        });

        // 用来存放第一层分类
        ArrayList<CourseType> firstCourseTypes = new ArrayList<>();
        allCourseTypes.forEach(courseType -> {
            if (courseType.getPid() == null || courseType.getPid().longValue() == 0L){ // 找出第一层分类
                firstCourseTypes.add(courseType);
            } else { // 非第一层
                // 查询自己的父分类
                CourseType courseType2 = (CourseType) allCourseTypeMap.get(courseType.getPid());
                if (courseType2 != null){
                    // 将自己添加到父分类中
                    // 注：因为地址是相同的，所以修改这个就等同于修改所有
                    courseType2.getChildren().add(courseType);
                }
            }
        });
        return firstCourseTypes;
    }

    /**
     * 获取门户展示类型--面包屑
     *   流程：
     *      1.根据id查询课程分类
     *      2.拿到课程分类的path切割
     *      3.根据parh批量查询课程分类
     *      4.循环分类，一个分类封装成一个CrumbsVo
     *      5.查询每个分类的同级分类，添加到CrumbsVo中
     *      6.将所有的CrumbsVo添加到一个list中响应给前端
     * @param id
     * @return
     */
    @Override
    public List<CrumbsVo> crumbs(Long id) {
        // 1.根据id查询课程分类
        CourseType courseType = super.selectById(id);
        AssertUtil.isNotNull(courseType, "无该课程，请重新选择");

        // 2.拿到课程分类的path切割
        String[] ids = courseType.getPath().split("\\.");

        // 3.根据parh批量查询课程分类
        List<CourseType> courseTypes = super.selectBatchIds(Arrays.asList(ids));

        // 4.循环分类，一个分类封装成一个CrumbsVo
        ArrayList<CrumbsVo> vos = new ArrayList<>();
        courseTypes.forEach(courseType1 -> {
            CrumbsVo crumbsVo = new CrumbsVo();
            crumbsVo.setOwnerCourseType(courseType1);
            // 5.查询每个分类的同级分类，添加到CrumbsVo中
            List<CourseType> types = courseTypeMapper.selectBatchPid(courseType1.getPid());
            crumbsVo.setOtherCourseTypes(types);
            // 6.将所有的CrumbsVo添加到一个list中响应给前端
            vos.add(crumbsVo);
        });
        return vos;
    }
}
